#include "filesystem.h"
#include <stdlib.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <vector>
#include "lib/e7convert.h"


FileSystem* FileSystem::onlyInstance_ = 0;

FileSystem* FileSystem::instance()
{
	if(onlyInstance_ == 0)
	{
		onlyInstance_ = new FileSystem();
	}

	return onlyInstance_;
}

FileSystem::FileSystem()
{
}

void FileSystem::mountFS(const char * fs)
{
    inodeBlocks = new vector<InodeBlock*>();
    freeListBlocks = new vector<FreeListBlock*>();


    if( (device = fopen(fs, "r")) == NULL)
    {
        std::cout << "Could not open input file";
        exit(-1);
    }

    loadSuperBlock();
    loadFreeListBlocks();
    loadInodeBlocks();
    //processInode(2);
    //cout << getAbsolutePath(2, 11) << endl;
    //dumpDirTree();
    //processPath("/gigafile");
    //processPath("/filsys");

    //dumpDirTree();
    //processPath("/asdsd");

    //cout << "----------------------" << endl;
    //InodeBlock* test = new InodeBlock(device, 31232);
    //InodeBlock* test = new InodeBlock(device, 1024);
    //test->print();

    //InodeBlock * inodeBlock = new InodeBlock(device, 13720);
    //inodeBlock->print();
}

FileSystem::~FileSystem()
{
    delete superBlock;

    for(int i = 0; i < inodeBlocks->size(); i++)
    {
        delete (*inodeBlocks)[i];
    }

    delete inodeBlocks;

    for(int i = 0; i < freeListBlocks->size(); i++)
    {
        delete (*freeListBlocks)[i];
    }

    delete freeListBlocks;

    onlyInstance_ = NULL;
}


void FileSystem::loadSuperBlock()
{
    superBlock = new SuperBlock(device);
    superBlock->print();
}

void FileSystem::loadInodeBlocks()
{
    InodeBlock * inodeBlock;
    for (int i=0; i<superBlock->getInodeSize(); i++)
    {
    	//cout << "Read Inode Blocks at: " << INODEBLOCK_OFFSET + INODEBLOCK_SIZE * i << endl;
        inodeBlock = new InodeBlock(device, INODEBLOCK_OFFSET + INODEBLOCK_SIZE * i);
        inodeBlocks->push_back(inodeBlock);
    }

    //cout << "HIER" << endl;
    //cout << inodeBlocks->size() << endl;
}

void FileSystem::processInode(int inodeNr)
{
	int inodeBlockNr = (inodeNr - 1) / 8;
	int inodeNrInBlock = (int)(((inodeNr) + 15) & 07);

	std::cout << "-------------------------------------------\n";
	//cout << "Inode Block Nr: " << inodeBlockNr << endl;
	//cout << "Inode Nr in Block: " << inodeNrInBlock << endl;
	std::cout << "Inode: " << inodeNr << "  " << getAbsolutePath(2,inodeNr) << endl;

	//get the inode..
	Inode* inode = inodeBlocks->at(inodeBlockNr)->getInodes()->at(inodeNrInBlock);
	inode->print();
	std::cout << "-------------------------------------------\n";

	//directory
	if((inode->getModeTypeFile() & 0170000) == 0040000)
	{
		//go process dir
		listDir(inode);
	}
	//file
	else if((inode->getModeTypeFile() & 0170000) == 0100000)
	{
		showData(inode);
		//go process file
	}
	// something else
	else
	{
		std::cout << "Sorry! Type unknown" << endl;
	}
}

//print content of (dir) inode
void FileSystem::listDir(Inode* inode)
{
	std::vector<long> * blockAddress = inode->getAllDataBlockAdresses();

	for(int i=0; i<blockAddress->size(); i++)
	{
		DirectoryBlock * dirBlock = new DirectoryBlock(device, (*blockAddress)[i] * BLOCKSIZE);
		dirBlock->print();
	}
}

//print content of (sys) inode
void FileSystem::showData(Inode* inode)
{
	vector<long> * blockAddress = inode->getAllDataBlockAdresses();
	for(int i=0; i<blockAddress->size(); i++)
	{

		if(fseek(device, (*blockAddress)[i]*BLOCKSIZE, SEEK_SET) < 0)
		{
			std::cout << "Seek failed." << endl;
			throw;
		}

		unsigned char block[512];
		fread(block, 512 ,1, device);

		for(int l=0; l<512; l++)
		{
			cout << block[l];
		}
	}

	cout << "\n";
}

std::string FileSystem::getAbsolutePath(int startNode, int inodeNr)
{
	std::string path = "";

	if(startNode == 2)
	{
		path += "/";
	}

	int inodeBlockNr = (startNode - 1 ) / 8;
	int inodeNrInBlock = (int)(((startNode) + 15) & 07);

	Inode* inode = inodeBlocks->at(inodeBlockNr)->getInodes()->at(inodeNrInBlock);

	if((inode->getModeTypeFile() & 0170000) == 0040000)
	{
		unsigned long * addresses = inode->getDataBlockAdresses();

		for(int k = 0; k < INODE_DATALIST_SIZE; k++)
		{
			unsigned long blockAddress = addresses[k];

			if(blockAddress != 0)
			{
				std::vector<Directory*>* dirs;
				DirectoryBlock * dirBlock = new DirectoryBlock(device, blockAddress * BLOCKSIZE);
				dirs = dirBlock->getDirs();

				//ignore . and ..
				for(int j=2; j<dirs->size(); j++)
				{
					if((*dirs)[j]->getInodeNr() == inodeNr)
					{
						path += (*dirs)[j]->getFilename();
						return path;
					}
					else
					{
						std::string subPath = getAbsolutePath((*dirs)[j]->getInodeNr(), inodeNr);

						if(subPath != "")
						{
							path += (*dirs)[j]->getFilename() + "/" + subPath;
							return path;
						}
					}
				}
			}
		}
	}
	return path;
}

bool FileSystem::processPath(string path)
{
	std::vector<std::string>* tokens = new std::vector<std::string>();

	while(path.find("/", 0) != std::string::npos)
	{
		size_t pos = path.find("/", 0);
		std::string temp = path.substr(0, pos);
		path.erase(0, pos+1);
		tokens->push_back(temp);
	}

	tokens->push_back(path);
	int currentNodeNr = 2;

	for(vector<string>::iterator iter = tokens->begin(); iter != tokens->end(); ++iter)
	{
		if((*iter) != "")
		{
			int searchNr = findInode(currentNodeNr, (*iter));
			std::cout << "searching: " << (*iter) << " Got: " << searchNr << "\n";

			if(searchNr>0)
			{
				currentNodeNr = searchNr;
			}
			else
			{
				std::cerr << "File not found\n";
				return false;
			}
			//cout << "Path: " << (*iter) << endl;
		}
	}

	std::cout << "Inode found: " << currentNodeNr << "\n";
	processInode(currentNodeNr);
	return true;
}

int FileSystem::findInode(int startNode, string inodeName)
{
	int returnValue = -1;
	int inodeBlockNr = (startNode-1) / 8;
	int inodeNrInBlock = (int)(((startNode) + 15) & 07);

	Inode* inode = inodeBlocks->at(inodeBlockNr)->getInodes()->at(inodeNrInBlock);

	if((inode->getModeTypeFile() & 0170000) == 0040000)
	{
		unsigned long * addresses = inode->getDataBlockAdresses();

		for(int k=0; k<INODE_DATALIST_SIZE; k++)
		{
			unsigned long blockAddress = addresses[k];

			if(blockAddress != 0)
			{
				std::vector<Directory*>* dirs;
				DirectoryBlock * dirBlock = new DirectoryBlock(device, blockAddress * BLOCKSIZE);
				dirs = dirBlock->getDirs();

				//ignore . and ..
				for(int j=2; j<dirs->size(); j++)
				{
					if((*dirs)[j]->getFilename() == inodeName)
					{
						returnValue = (*dirs)[j]->getInodeNr();
						return returnValue;
					}
				}
			}
		}
	}

	return returnValue;
}

void FileSystem::loadFreeListBlocks()
{
    std::cout << "---------------------------------------\n";
    std::cout << "Dump of freelist on " << superBlock->getFileSystemName() << "." << superBlock->getSystemPackName() << ":\n";
    std::cout << "---------------------------------------\n";
    std::cout << "In superblock:\n";
    std::cout << "Holds " << superBlock->getFreeListEntries()<< " entries:\n";

    ulong * freeList = superBlock->getFreeBlockList();
    unsigned long  nextFreeBlockOffset = freeList[0] * BLOCKSIZE;

    for(int i=0; i<superBlock->getFreeListEntries(); i++)
    {
        std::cout << freeList[i];
    }

    printf("\n");

    FreeListBlock * freeListBlock;
    ulong * addresses;

    do
    {
        std::cout << "Fetching freeblock: " << (nextFreeBlockOffset / BLOCKSIZE) << "\n";

        freeListBlock = new FreeListBlock(device, nextFreeBlockOffset);
        freeListBlocks->push_back(freeListBlock);
        freeListBlock->print();

        addresses = freeListBlock->getFreeListBlockAddresses();
        nextFreeBlockOffset = addresses[0] * BLOCKSIZE;
    }
    while(addresses[0] != 0);
}

void FileSystem::dumpDirTree()
{
	std::cout << "---------------------------------------\n";
	std::cout << "Directory tree on floppy: \n";
	std::cout << "---------------------------------------\n";
	std::vector<Inode*>* inodes;

	for(int i=0; i<inodeBlocks->size(); i++)
	{
		inodes = (*inodeBlocks)[i]->getInodes();

		for(int j=0; j<inodes->size(); j++)
		{
			Inode * inode = (*inodes)[j];
			int inodeNr = ((i * 512) + (j * 64)) / 64 + 1;

			if((inode->getModeTypeFile() & 0170000) == 0040000)
			{
				std::cout << "Inode: " << inodeNr << "  " << getAbsolutePath(2,inodeNr) << "\n";
				//inode->print();
				listDir(inode);
				std::cout << "\n";
			}
		}
	}
}

void FileSystem::dumpInodeTree()
{
	std::cout << "---------------------------------------\n";
	std::cout << "Inode tree on floppy: \n";
	std::cout << "---------------------------------------\n";
	vector<Inode*>* inodes;

	for(int i=0; i<inodeBlocks->size(); i++)
	{
		inodes = (*inodeBlocks)[i]->getInodes();

		for(int j = 0; j < inodes->size(); j++)
		{
			Inode * inode = (*inodes)[j];
			int inodeNr = ((i*512)+(j*64))/64+1;

			if(((inode->getModeTypeFile() & 0170000) == 0040000) || ((inode->getModeTypeFile() & 0170000) == 0100000))
			{
				std::cout << "-------------------------------------------\n";
				std::cout << "Inode: " << inodeNr << "  " << getAbsolutePath(2,inodeNr) << "\n";
				inode->print();
				//listDir(inode);
				//printf("\n");
			}
		}
	}
}





std::vector<Inode*> FileSystem::getInodesFromDir(string path)
{

}

void FileSystem::print()
{

}

